import json
import time
from datetime import datetime
from decimal import Decimal

import ccxt
from apscheduler.executors.asyncio import AsyncIOExecutor
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from ccxt import Exchange

from loguru import logger
from sqlalchemy import desc
from sqlalchemy.orm import Session

from app.api import deps
from app.common.dateutils import DateUtils
from app.core.config import settings
from app.core.constant_code import ConstantItem, ExchangeType
from app.core.dingtalk import RobotFactory
from app.core.exception_code import ExceptionCode, BasicExceptionCode
from app.core.exceptions import BizException
from app.models.bar import Bar
from app.models.instrument import Instrument
from app.spider.spider import Spider


class BinanceSpier(Spider):
    def __init__(self):
        self.client = None

    def getClient(self) -> ccxt.binance:
        if self.client:
            return self.client

        options = {
            'timeout': 30000,
            'enableRateLimit': True,
        }
        if settings.PROXY_HOST and settings.PROXY_PORT:
            options['proxies'] = {
                'http': f'http://{settings.PROXY_HOST}:{settings.PROXY_PORT}',
                'https': f'http://{settings.PROXY_HOST}:{settings.PROXY_PORT}',
            }
        return ccxt.binance(options)


class BinanceInstrumentSpider(BinanceSpier):
    def __init__(self, db):
        super(BinanceInstrumentSpider,self).__init__()
        self.db = db
        self.client: ccxt.binance = self.getClient()

    def crawl(self):
        current = None
        try:
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(f"[{datetime.now()}] 商品采集(BinanceInstrumentSpider),任务开始......")
            else:
                logger.info('无法向钉钉发送消息......')

            markets = self.getClient().load_markets()
            # 暂时只爬取永续合约
            linear_swaps = list(filter(lambda mkt: mkt['swap'] and mkt['linear'] and mkt['active'], markets.values()))
            totalTimeStart = time.time()
            for index, item in enumerate(linear_swaps):
                current = item
                instrument = Instrument()
                instrument.base = item['base']
                instrument.quote = item['quote']
                instrument.symbol = item['symbol']
                instrument.name = item['symbol']
                instrument.precision = json.dumps(item['precision'])
                instrument.limits = json.dumps(item['limits'])
                instrument.nameLetter = item['symbol']
                instrument.areaId = 1
                instrument.exchangeId = 1
                # instrument.area = row['area']
                # instrument.industry = row['industry']
                # instrument.symbolPartition = row['market']
                # instrument.pricePrecision = symbol['baseAssetPrecision']
                # instrument.amountPrecision = symbol['baseAssetPrecision']
                # instrument.marketType = ConstantItem.MARKET_CRYPTOCURRENCY
                # instrument.nameLetter = symbol['symbol']
                # instrument.exchange_id = self.exchange
                # instrument.countryCode = self.countryCode

                dbInstrument = self.db.query(Instrument).filter_by(areaId=instrument.areaId,
                                                                   exchangeId=instrument.exchangeId,
                                                                   symbol=instrument.symbol).one_or_none()
                if dbInstrument is not None:
                    dbInstrument.name = instrument.name
                    dbInstrument.base = instrument.base
                    dbInstrument.quote = instrument.quote
                    dbInstrument.nameLetter = instrument.nameLetter
                    self.db.commit()
                else:
                    self.db.add(instrument)
                    self.db.commit()
            self.db.close()
            msg = f"[{datetime.now()}] 商品采集(BinanceInstrumentSpider)=> 总共处理{len(linear_swaps)}个商品，耗时{time.time() - totalTimeStart}，成功{len(linear_swaps)}个，失败0个，失败商品列表：[]"
            logger.info(msg)
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(msg)
        except Exception as ex:
            logger.error("商品采集(BinanceInstrumentSpider)任务异常：" + json.dumps(current))
            logger.exception(ex)
        finally:
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(f"[{datetime.now()}] 商品采集(BinanceInstrumentSpider),任务结束......")


class BinanceBarSpider(BinanceSpier):
    def __init__(self, db, frequency, startDate, instruments=[]):
        super().__init__()
        self.db = db
        self.exchangeId = ExchangeType.BINANCE
        self.limit = 1500
        self.frequency = frequency
        self.startDate = DateUtils.dateToTimestamp13(startDate)
        self.instruments = instruments if len(instruments) > 0 else self.getInstruments()
        self.client: ccxt.binance = self.getClient()

    def getInstruments(self):
        return self.db.query(Instrument).filter_by(exchangeId=self.exchangeId).all()

    def formatField(self, origin, bar):
        bar.barDate = DateUtils.timestamp13ToDate(origin[0])
        # fetch_ohlcv
        bar.open = Decimal(str(origin[1]))
        bar.high = Decimal(str(origin[2]))
        bar.low = Decimal(str(origin[3]))
        bar.close = Decimal(str(origin[4]))
        bar.volume = Decimal(str(origin[5]))
        return bar


class DefaultBinanceBarSpier(BinanceBarSpider):
    def __init__(self, db, frequency, startDate, instruments=[]):
        super(DefaultBinanceBarSpier, self).__init__(db, frequency, startDate, instruments)


    def crawl(self):
        handleCount = 0
        total = len(self.instruments)
        totalTimeStart = time.time()
        limit = 1500
        errors = []
        try:
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(f"[{datetime.now()}] K线（{self.frequency}）采集(BinanaceBarSpier),任务开始运行......")
            logger.info(f"[{datetime.now()}] K线（{self.frequency}）采集(BinanceBarSpier),任务开始运行......")
            for instrument in self.instruments:
                try:
                    handleCount += 1
                    logger.info('{}-{}，当前进度{}%,总{},已采集{}', instrument.name, self.frequency,round(handleCount * 100 / total, 2), total, handleCount)
                    # 先查出库中最后一条数据
                    dbBar = self.db.query(Bar).filter(Bar.instrumentId == instrument.id,
                                                      Bar.frequency == self.frequency,
                                                      Bar.adjustFlag == ConstantItem.ADJUST_NO).order_by(desc(Bar.barDate)).first()
                    # TODO
                    startDate = DateUtils.dateToTimestamp13(dbBar.barDate) if dbBar else self.startDate
                    endDate = DateUtils.dateToTimestamp13(DateUtils.tomorrow())
                    rs = []
                    while startDate < endDate:
                        result = self.client.fetch_ohlcv(instrument.symbol, getApiFrequency(self.frequency), startDate, limit)
                        rs += result
                        logger.info('{}-{}已采集{}条', instrument.name, self.frequency, len(rs))
                        # 获取最后一条数据的时间
                        if len(result) == 0:
                            break
                        startDate = rs[-1][0] + frequencyUnit(self.frequency)
                    start = time.time()
                    if len(rs) == 0:
                        logger.info('{}-{}没有数据', instrument.name, self.frequency)
                    logger.info('获取{}条{}-{}数据,耗时{}秒', len(rs), instrument.symbol, self.frequency, time.time() - start)
                    bars = []
                    # 处理数据耗时
                    start = time.time()
                    count = 0
                    preRow = dbBar
                    if len(rs) > 0:
                        # 处理第一条数据，第一条有数据，做更新，无数据，做插入
                        first = rs[0]
                        if preRow:
                            # 做更新
                            if first[0] == DateUtils.dateToTimestamp13(preRow.barDate):
                                preRow = self.formatField(first, preRow)
                                preRow.change = Decimal(str(first[4])) - preRow.preClose
                                preRow.pctChange = round(preRow.change * 100 / preRow.preClose, 4)
                                preRow.amount = 0
                            else:
                                preRow = self.formatField(first, Bar())
                                preRow.adjustFlag = ConstantItem.ADJUST_NO
                                preRow.frequency = self.frequency
                                preRow.instrumentId = instrument.id
                                preRow.tradeStatus = 1
                                preRow.preClose = dbBar.close
                                preRow.change = Decimal(str(preRow.close)) - Decimal(str(preRow.preClose))
                                preRow.pctChange = round(preRow.change * 100 / preRow.preClose, 4)
                                preRow.amount = 0
                                self.db.add(preRow)
                        else:
                            # 插入
                            preRow = self.formatField(first, Bar())
                            preRow.adjustFlag = ConstantItem.ADJUST_NO
                            preRow.frequency = self.frequency
                            preRow.instrumentId = instrument.id
                            preRow.tradeStatus = 1
                            preRow.change = 0
                            preRow.preClose = first[4]
                            preRow.pctChange = 0
                            preRow.amount = 0

                            self.db.add(preRow)
                    self.db.commit()

                    for i, row in enumerate(rs):
                        if i == 0:
                            preRow = row
                            continue
                        bar = self.formatField(row, Bar())
                        bar.adjustFlag = ConstantItem.ADJUST_NO
                        bar.frequency = self.frequency
                        bar.instrumentId = instrument.id
                        bar.tradeStatus = 1
                        bar.change = Decimal(str(bar.close)) - Decimal(str(preRow[4]))
                        bar.preClose = Decimal(preRow[4])
                        bar.amount = 0
                        bar.pctChange = round(bar.change * 100 / bar.preClose, 4)
                        bars.append(bar)
                        # 分段插入
                        if len(bars) == 200000 or i == len(rs) - 1:
                            count = count + len(bars)
                            logger.info('已处理{}条{}-{}数据耗时{}秒', count, instrument.symbol, self.frequency, time.time() - start)
                            self.db.bulk_save_objects(bars)
                            # self.db.flush()
                            self.db.commit()
                            bars.clear()
                        preRow = row
                    # self.db.flush()
                    self.db.commit()
                    logger.info('总共{}条({},{})-{}数据,耗时{}秒', len(rs), instrument.symbol, instrument.name, self.frequency,
                                time.time() - start)
                except Exception as ex:
                    msg = f'{instrument.symbol}-{self.frequency}'
                    logger.error(f"{msg}处理失败")
                    logger.exception(ex)
                    errors.append(msg)
            msg = f'[{datetime.now()}] K线（{self.frequency}）爬取(BinanceBarSpier) => 总共处理{len(self.instruments)}个商品，耗时{time.time() - totalTimeStart}，成功{len(self.instruments) - len(errors)}个，失败{len(errors)}个，失败商品列表：[{errors}]'
            logger.info(msg)
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(msg)
        except Exception as ex:
            logger.info(f"[{datetime.now()}] K线采集(BinanceBarSpier),采集失败......")
            logger.exception(ex)
        finally:
            logger.info(f"[{datetime.now()}] K线采集(BinanceBarSpier),任务结束......")
            robot = RobotFactory.getAvailable()
            if robot:
                robot.sendText(f"[{datetime.now()}] K线采集(BinanceBarSpier),任务结束......")

def getApiFrequency(frequency):
    # if frequency == ConstantItem.FREQUENCY_1S:
    #     return '1s'
    if frequency == ConstantItem.FREQUENCY_1MIN:
        return '1m'
    elif frequency == ConstantItem.FREQUENCY_3MIN:
        return '3m'
    elif frequency == ConstantItem.FREQUENCY_5MIN:
        return '5m'
    elif frequency == ConstantItem.FREQUENCY_15MIN:
        return '15m'
    elif frequency == ConstantItem.FREQUENCY_30MIN:
        return '30m'
    elif frequency == ConstantItem.FREQUENCY_1HOUR:
        return '1h'
    elif frequency == ConstantItem.FREQUENCY_2HOUR:
        return '2h'
    elif frequency == ConstantItem.FREQUENCY_4HOUR:
        return '4h'
    elif frequency == ConstantItem.FREQUENCY_6HOUR:
        return '6h'
    elif frequency == ConstantItem.FREQUENCY_8HOUR:
        return '8h'
    elif frequency == ConstantItem.FREQUENCY_12HOUR:
        return '12h'
    elif frequency == ConstantItem.FREQUENCY_1D:
        return '1d'
    elif frequency == ConstantItem.FREQUENCY_1W:
        return '1w'
    else:
        raise BizException(BasicExceptionCode.ILLEGAL_PARAMETER)

def frequencyUnit(frequency):
    if frequency == ConstantItem.FREQUENCY_1S:
        return 1000
    if frequency == ConstantItem.FREQUENCY_1MIN:
        return 60000
    elif frequency == ConstantItem.FREQUENCY_3MIN:
        return 180000
    elif frequency == ConstantItem.FREQUENCY_5MIN:
        return 300000
    elif frequency == ConstantItem.FREQUENCY_15MIN:
        return 900000
    elif frequency == ConstantItem.FREQUENCY_30MIN:
        return 1800000
    elif frequency == ConstantItem.FREQUENCY_1HOUR:
        return 3600000
    elif frequency == ConstantItem.FREQUENCY_2HOUR:
        return 7200000
    elif frequency == ConstantItem.FREQUENCY_4HOUR:
        return 14400000
    elif frequency == ConstantItem.FREQUENCY_6HOUR:
        return 21600000
    elif frequency == ConstantItem.FREQUENCY_8HOUR:
        return 28800000
    elif frequency == ConstantItem.FREQUENCY_12HOUR:
        return 43200000
    elif frequency == ConstantItem.FREQUENCY_1D:
        return 86400000
    elif frequency == ConstantItem.FREQUENCY_1W:
        return 604800000
    else:
        raise BizException(BasicExceptionCode.ILLEGAL_PARAMETER)


if __name__ == '__main__':
#     # 1.采集商品信息
    db: Session = next(deps.getDb())
    # spider = BinanceInstrumentSpider(db)
    # spider.crawl()
#
#     # 2.采集K线
    instruments = db.query(Instrument).filter(Instrument.exchangeId==1).all()
    # instruments = db.query(Instrument).filter(Instrument.id.in_([102,103])).all()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_1D, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_12HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_8HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_6HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_4HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_2HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_1HOUR, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_30MIN, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_15MIN, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_5MIN, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_3MIN, DateUtils.parse("2020-01-01"), instruments)
    spier.crawl()
    spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_1MIN, DateUtils.parse("2020-01-01"),instruments)
    spier.crawl()









    # spier = DefaultBinanceBarSpier(db, ConstantItem.FREQUENCY_1W, DateUtils.parse("2020-01-01"),instruments)
    # spier.crawl()


